#pragma once
#include "MathUtils.h"
namespace Alice{
    /**
     * A 4D homogeneous vector, 4x1 FLOATs, 16-byte aligned.
     */
    template<typename T>
    struct alignas(16) TVector4
    {
        // Can't have a UE_REQUIRES in the declaration because of the forward declarations, so check for allowed types here.
        static_assert(std::is_floating_point_v<T>, "TVector4 only supports float and double types.");


    public:
        using FReal = T;

        union
        {
            struct
            {
                /** The vector's X-component. */
                T X;

                /** The vector's Y-component. */
                T Y;

                /** The vector's Z-component. */
                T Z;

                /** The vector's W-component. */
                T W;
            };
            T XYZW[4];
        };

    public:

        /**
         * Constructor from 3D TVector. W is set to 1.
         *
         * @param InVector 3D Vector to set first three components.
         */
        TVector4(const TVector<T>& InVector);

        /**
         * Constructor.
         *
         * @param InVector 3D Vector to set first three components.
         * @param InW W Coordinate.
         */
        template<typename FArg>
        FORCEINLINE TVector4(const TVector<T>& InVector, FArg InW)
            : X(InVector.X)
            , Y(InVector.Y)
            , Z(InVector.Z)
            , W((T)InW)
        {
            DiagnosticCheckNaN();
        }

        /**
         * Constructor allowing copying of an TVector4 whilst setting up a new W component.
         * @param InVector 4D Vector to set first three components.
         * @param InOverrideW Replaces W Coordinate of InVector.
         */

        template<typename FArg>
        FORCEINLINE TVector4(const TVector4<T>& InVector, FArg OverrideW)
            : X(InVector.X)
            , Y(InVector.Y)
            , Z(InVector.Z)
            , W((T)OverrideW)
        {
            DiagnosticCheckNaN();
        }

        /**
         * Creates and initializes a new vector from a color value.
         *
         * @param InColour Color used to set vector.
         */
        FORCEINLINE TVector4(const FLinearColor& InColor)
            : X(InColor.R)
            , Y(InColor.G)
            , Z(InColor.B)
            , W(InColor.A)
        {
            DiagnosticCheckNaN();
        }

        /**
         * Creates and initializes a new vector from a color RGB and W
         *
         * @param InColour Color used to set XYZ.
         * @param InOverrideW
         */
        FORCEINLINE TVector4(const FLinearColor& InColor, T InOverrideW)
            : X(InColor.R)
            , Y(InColor.G)
            , Z(InColor.B)
            , W(InOverrideW)
        {
            DiagnosticCheckNaN();
        }

        /**
         * Creates and initializes a new vector from the specified components.
         *
         * @param InX X Coordinate.
         * @param InY Y Coordinate.
         * @param InZ Z Coordinate.
         * @param InW W Coordinate.
         *
         * NOTE: This default constructor is unlike TVector, TMatrix etc. in that it
         *       actually initializes the instance.  Ideally it should be = default;
         *       in the same way, but this would break backwards compatibility.
         */
        explicit TVector4(T InX = 0.0f, T InY = 0.0f, T InZ = 0.0f, T InW = 1.0f);

        /**
         * Creates and initializes a new vector from the specified 2D vectors.
         *
         * @param InXY A 2D vector holding the X- and Y-components.
         * @param InZW A 2D vector holding the Z- and W-components.
         */
        explicit TVector4(TVector2<T> InXY, TVector2<T> InZW);

        /**
         * Creates and initializes a new vector from an int vector value.
         *
         * @param InVector IntVector used to set vector.
         */
        TVector4(const FIntVector4& InVector);

        /**
         * Creates and initializes a new vector to zero.
         *
         * @param EForceInit Force Init Enum.
         */
        explicit TVector4(EForceInit);

        /**
         * Creates an uninitialized new vector.
         *
         * @param ENoInit Force uninitialized enum.
         */
        TVector4(ENoInit);

    public:

        // To satisfy UE::Geometry type
        static TVector4<T> Zero()
        {
            return TVector4(T(0), T(0), T(0), T(0));
        }

        // To satisfy UE::Geometry type
        static TVector4<T> One()
        {
            return TVector4(T(1), T(1), T(1), T(1));
        }

        /**
         * Access a specific component of the vector.
         *
         * @param ComponentIndex The index of the component.
         * @return Reference to the desired component.
         */
        FORCEINLINE T& operator[](int32 ComponentIndex);

        /**
         * Access a specific component of the vector.
         *
         * @param ComponentIndex The index of the component.
         * @return Copy of the desired component.
         */
        FORCEINLINE T operator[](int32 ComponentIndex) const;

        // Unary operators.

        /**
         * Gets a negated copy of the vector.
         *
         * @return A negated copy of the vector.
         */
        FORCEINLINE TVector4<T> operator-() const;

        /**
         * Gets the result of adding a vector to this.
         *
         * @param V The vector to add.
         * @return The result of vector addition.
         */
        FORCEINLINE TVector4<T> operator+(const TVector4<T>& V) const;

        /**
         * Adds another vector to this one.
         *
         * @param V The other vector to add.
         * @return Copy of the vector after addition.
         */
        FORCEINLINE TVector4<T> operator+=(const TVector4<T>& V);

        /**
         * Gets the result of subtracting a vector from this.
         *
         * @param V The vector to subtract.
         * @return The result of vector subtraction.
         */
        FORCEINLINE TVector4<T> operator-(const TVector4<T>& V) const;

        /**
         * Subtracts another vector to this one.
         *
         * @param V The other vector to subtract.
         * @return Copy of the vector after subtraction.
         */
        FORCEINLINE TVector4<T> operator-=(const TVector4<T>& V);

        /**
         * Gets the result of scaling this vector.
         *
         * @param Scale The scaling factor.
         * @return The result of vector scaling.
         */
        template<typename FArg UE_REQUIRES(std::is_arithmetic_v<FArg>)>
        FORCEINLINE TVector4<T> operator*(FArg Scale) const
        {
            return TVector4(X * Scale, Y * Scale, Z * Scale, W * Scale);
        }

        /**
         * Gets the result of dividing this vector.
         *
         * @param Scale What to divide by.
         * @return The result of division.
         */
        template<typename FArg UE_REQUIRES(std::is_arithmetic_v<FArg>)>
        FORCEINLINE TVector4<T> operator/(FArg Scale) const
        {
            const T RScale = T(1.0f) / Scale;
            return TVector4(X * RScale, Y * RScale, Z * RScale, W * RScale);
        }

        /**
         * Gets the result of dividing this vector.
         *
         * @param V What to divide by.
         * @return The result of division.
         */
        TVector4<T> operator/(const TVector4<T>& V) const;

        /**
         * Gets the result of multiplying a vector with this.
         *
         * @param V The vector to multiply.
         * @return The result of vector multiplication.
         */
        TVector4<T> operator*(const TVector4<T>& V) const;

        /**
         * Gets the result of multiplying a vector with another Vector (component wise).
         *
         * @param V The vector to multiply.
         * @return The result of vector multiplication.
         */
        TVector4<T> operator*=(const TVector4<T>& V);

        /**
         * Gets the result of dividing a vector with another Vector (component wise).
         *
         * @param V The vector to divide with.
         * @return The result of vector multiplication.
         */
        TVector4<T> operator/=(const TVector4<T>& V);

        /**
         * Gets the result of scaling this vector.
         *
         * @param Scale The scaling factor.
         * @return The result of vector scaling.
         */
        template<typename FArg>
        FORCEINLINE TVector4<T> operator*=(FArg Scale)
        {
            X *= Scale; Y *= Scale; Z *= Scale; W *= Scale;
            DiagnosticCheckNaN();
            return *this;
        }

        /**
         * Gets the result of scaling this vector by 1/Scale.
         *
         * @param Scale The inverse scaling factor.
         * @return The result of vector scaling by 1/Scale.
         */
        template<typename FArg>
        FORCEINLINE TVector4<T> operator/=(FArg Scale)
        {
            const T RV = T(1.0f) / Scale;
            X *= RV; Y *= RV; Z *= RV; W *= RV;
            DiagnosticCheckNaN();
            return *this;
        }

        /**
         * Checks for equality against another vector.
         *
         * @param V The other vector.
         * @return true if the two vectors are the same, otherwise false.
         */
        bool operator==(const TVector4<T>& V) const;

        /**
         * Checks for inequality against another vector.
         *
         * @param V The other vector.
         * @return true if the two vectors are different, otherwise false.
         */
        bool operator!=(const TVector4<T>& V) const;

        /**
         * Calculate Cross product between this and another vector.
         *
         * @param V The other vector.
         * @return The Cross product.
         */
        TVector4<T> operator^(const TVector4<T>& V) const;

    public:

        // Simple functions.

        /**
         * Gets a specific component of the vector.
         *
         * @param Index The index of the component.
         * @return Reference to the component.
         */
        T& Component(int32 Index);

        /**
        * Gets a specific component of the vector.
        *
        * @param Index The index of the component.
        * @return Reference to the component.
        */
        const T& Component(int32 Index) const;

        /**
         * Error tolerant comparison.
         *
         * @param V Vector to compare against.
         * @param Tolerance Error Tolerance.
         * @return true if the two vectors are equal within specified tolerance, otherwise false.
         */
        bool Equals(const TVector4<T>& V, T Tolerance=UE_KINDA_SMALL_NUMBER) const;

        /**
         * Check if the vector is of unit length, with specified tolerance.
         *
         * @param LengthSquaredTolerance Tolerance against squared length.
         * @return true if the vector is a unit vector within the specified tolerance.
         */
        bool IsUnit3(T LengthSquaredTolerance = UE_KINDA_SMALL_NUMBER) const;

        /**
         * Get a textual representation of the vector.
         *
         * @return Text describing the vector.
         */
        FString ToString() const;

        /**
         * Initialize this Vector based on an FString. The String is expected to contain X=, Y=, Z=, W=.
         * The TVector4 will be bogus when InitFromString returns false.
         *
         * @param InSourceString	FString containing the vector values.
         * @return true if the X,Y,Z values were read successfully; false otherwise.
         */
        bool InitFromString(const FString& InSourceString);

        /**
         * Returns a normalized copy of the vector if safe to normalize.
         *
         * @param Tolerance Minimum squared length of vector for normalization.
         * @return A normalized copy of the vector or a zero vector.
         */
        FORCEINLINE TVector4 GetSafeNormal(T Tolerance = UE_SMALL_NUMBER) const;

        /**
         * Calculates normalized version of vector without checking if it is non-zero.
         *
         * @return Normalized version of vector.
         */
        FORCEINLINE TVector4 GetUnsafeNormal3() const;

        /**
         * Return the FRotator orientation corresponding to the direction in which the vector points.
         * Sets Yaw and Pitch to the proper numbers, and sets roll to zero because the roll can't be determined from a vector.
         * @return FRotator from the Vector's direction.
         */
        CORE_API TRotator<T> ToOrientationRotator() const;

        /**
         * Return the Quaternion orientation corresponding to the direction in which the vector points.
         * @return Quaternion from the Vector's direction.
         */
        CORE_API TQuat<T> ToOrientationQuat() const;

        /**
         * Return the FRotator orientation corresponding to the direction in which the vector points.
         * Sets Yaw and Pitch to the proper numbers, and sets roll to zero because the roll can't be determined from a vector.
         * Identical to 'ToOrientationRotator()'.
         * @return FRotator from the Vector's direction.
         * @see ToOrientationRotator()
         */
        FORCEINLINE TRotator<T> Rotation() const
        {
            return ToOrientationRotator();
        }

        /**
         * Set all of the vectors coordinates.
         *
         * @param InX New X Coordinate.
         * @param InY New Y Coordinate.
         * @param InZ New Z Coordinate.
         * @param InW New W Coordinate.
         */
        FORCEINLINE void Set(T InX, T InY, T InZ, T InW);

        /**
         * Get the length of this vector not taking W component into account.
         *
         * @return The length of this vector.
         */
        T Size3() const;

        /**
         * Get the squared length of this vector not taking W component into account.
         *
         * @return The squared length of this vector.
         */
        T SizeSquared3() const;

        /**
         * Get the length (magnitude) of this vector, taking the W component into account
         *
         * @return The length of this vector
         */
        T Size() const;

        /**
         * Get the squared length of this vector, taking the W component into account
         *
         * @return The squared length of this vector
         */
        T SizeSquared() const;

        /** Utility to check if there are any non-finite values (NaN or Inf) in this vector. */
        bool ContainsNaN() const;

        /** Utility to check if all of the components of this vector are nearly zero given the tolerance. */
        bool IsNearlyZero3(T Tolerance = ALICE_KINDA_SMALL_NUMBER) const;

        /** Reflect vector. */
        TVector4<T> Reflect3(const TVector4<T>& Normal) const;

        /**
         * Find good arbitrary axis vectors to represent U and V axes of a plane,
         * given just the normal.
         */
        void FindBestAxisVectors3(TVector4<T>& Axis1, TVector4<T>& Axis2) const;

    #if ENABLE_NAN_DIAGNOSTIC
        FORCEINLINE void DiagnosticCheckNaN()
        {
            if (ContainsNaN())
            {
                logOrEnsureNanError(TEXT("FVector4 contains NaN: %s"), *ToString());
                *this = TVector4();

            }
        }
    #else
        FORCEINLINE void DiagnosticCheckNaN() { }
    #endif
        // Conversion from other type: double->float
        template<typename FArg>
        explicit TVector4(const TVector4<FArg>& From) : TVector4<T>((T)From.X, (T)From.Y, (T)From.Z, (T)From.W) {}

        // Conversion from other type: float->double
        template<typename FArg>
        explicit TVector4(const TVector4<FArg>& From) : TVector4<T>((T)From.X, (T)From.Y, (T)From.Z, (T)From.W) {}
    };
    using Vector4d 	= TVector4<double>;
}